﻿using Dyao.Book.Core;
using Foundation.Core;
using Foundation.Entity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;

namespace Dyao.Book.Repository.Shared
{
    /// <summary>
    /// entity 设置创建、更新的用户及时间信息
    /// </summary>
    public class EntityPropertySetter : ITransientDependency
    {
        private readonly static List<(EntityState[], Type, Func<SetPropertyContext, object>)> _setPropertyList =
           new List<(EntityState[], Type, Func<SetPropertyContext, object>)>
       {
            (new []{ EntityState.Added } ,typeof(ICreateUserId),(context)=>context.UserId),
            (new []{ EntityState.Added } ,typeof(ICreateUserName),(context)=>context.UserName ),
            (new []{ EntityState.Added } ,typeof(ICreateOn),(context)=>context.DateTime),

            (new []{ EntityState.Added, EntityState.Modified },typeof(IUpdateUserId),(context)=>context.UserId),
            (new []{ EntityState.Added, EntityState.Modified },typeof(IUpdateUserName),(context)=>context.UserName ),
            (new []{ EntityState.Added, EntityState.Modified },typeof(IUpdateOn),(context)=>context.DateTime)
       };

        private readonly static ConcurrentDictionary<Type, List<Action<SetPropertyContext>>> _entityActionDict = new ConcurrentDictionary<Type, List<Action<SetPropertyContext>>>();

        private readonly ICurrentUser _currentUser;

        public EntityPropertySetter(ICurrentUser currentUser)
        {
            this._currentUser = currentUser;
        }

        /// <summary>
        /// 设置Entity的属性值。包括：
        /// 创建信息：CreateUserId、CreateUserName、CreateDateTime。
        /// 更新信息：UpdateUserId、UpdateUserName、UpdateDateTime。
        /// </summary>
        /// <param name="entityEntry"></param>
        public void SetProperties(EntityEntry entityEntry)
        {
            var actions = GetActions(entityEntry);

            if (actions != null && actions.Count > 0)
            {
                var context = new SetPropertyContext
                {
                    Entry = entityEntry,
                    UserId = Convert.ToInt32(_currentUser.Id),
                    UserName = _currentUser.UserName,
                    DateTime = DateTime.Now,
                };

                foreach (var action in actions)
                {
                    action(context);
                }
            }
        }

        private static IReadOnlyList<Action<SetPropertyContext>> GetActions(EntityEntry entityEntry)
        {
            return _entityActionDict.GetOrAdd(entityEntry.Metadata.ClrType, (entityType) =>
            {
                var properties = entityEntry.Metadata.GetProperties();
                var list = new List<Action<SetPropertyContext>>();

                foreach (var (states, interfaceType, getter) in _setPropertyList)
                {
                    var interfaceProperty = interfaceType.GetProperties().FirstOrDefault();
                    if (interfaceProperty == null) continue;

                    //查找entity属性中和接口的属性相同的第一个属性信息（名称和类型（去掉可空类型后）相同）
                    var p = properties.FirstOrDefault(a => a.Name == interfaceProperty.Name && UnwrapNullableType(a.ClrType) == interfaceProperty.PropertyType);
                    if (p == null) continue;

                    var isnull = p.IsNullable || (IsNullableValueType(p.ClrType));

                    var setter = CreateSetter(entityType, p.PropertyInfo);//创建设置entity属性值的action
                    if (setter == null) continue;

                    list.Add((context) =>
                    {
                        if (states.Contains(context.Entry.State))
                        {
                            var value = getter(context);

                            if (isnull && value == null) return;
                            if (isnull && value is int id && id == 0) return;

                            if (p.PropertyInfo.PropertyType == typeof(string))
                            {
                                if ((!isnull && value == null))
                                {
                                    value = string.Empty;
                                }
                                else if (isnull && value == null)
                                {
                                    return;
                                }
                            }

                            setter(context.Entry.Entity, value);
                        }
                    });
                }

                return list;
            });
        }

        private static Action<object, object> CreateSetter(Type entityType, PropertyInfo p)
        {

            if (p != null && p.CanWrite)
            {
                var entity = Expression.Parameter(typeof(object), "entity");
                var value = Expression.Parameter(typeof(object), "value");
                var setMethod = p.GetSetMethod();

                if (setMethod != null)
                {
                    var body = Expression.Call(Expression.Convert(entity, entityType), setMethod, Expression.Convert(value, p.PropertyType));
                    var setter = Expression.Lambda<Action<object, object>>(body, entity, value).Compile();

                    return setter;
                }

            }

            return null;
        }

        private static Type UnwrapNullableType(Type type) => Nullable.GetUnderlyingType(type) ?? type;

        private static bool IsNullableValueType(Type type)
            => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);

        protected class SetPropertyContext
        {
            public EntityEntry Entry { get; set; }

            public int UserId { get; set; }

            public string UserName { get; set; }

            public DateTime DateTime { get; set; }
        }
    }
}
